// See LICENSE.TXT for terms of use, and copyright information

#include <avr/io.h>
#include <avr/eeprom.h>
#include <string.h>
#include "common.h"
#include "vm.h"
#include "prand.h"
#include "sleep.h"

uint8_t pc, st, ist;			// program count, accumulator value, and status register
uint8_t program[VM_MAX_PROGSIZE];	// program space, uses char so we can just treat programs as strings
uint8_t reg[VM_NUM_REGISTERS];
uint32_t delay_val;			// so the VM can track it's delay time.

void load_program(uint8_t progno) {
	uint8_t curprog = 0;
	uint16_t byte_offset = 4;	// programs start @ the fifth eeprom byte, skipping the first 4

	while(1) {
		uint8_t thisbyte = eeprom_read_byte(byte_offset);
		byte_offset++;
		// this must be size of following program in bytes, and if not... its not valid eeprom
		if (thisbyte == 0x00 || thisbyte > VM_MAX_PROGSIZE || byte_offset+thisbyte >= VM_MAX_EESTORAGE) {
			// invalid or blank EEPROM, exit
			return;
		}

		if (curprog != progno) {
			byte_offset += thisbyte;
			curprog += 1;
			// having adjusted to past the current program and incremented, loop to try again
		} else {
			// we're pointing to the correct program, do something about it
			wipe_program();			// wipe the slate
			for (uint8_t i=0; i<thisbyte; i++) {
				program[i] = eeprom_read_byte(byte_offset);
				byte_offset++;
			}
			return;			// XXX need to differentiate this from a bad return...
		}
	
	}
}

void wipe_program() {
	pc=0;
	st=0;
	ist=0;
	delay_val=0;
	memset(program, 0, sizeof(program));
	memset(reg, 0, sizeof(reg));
}

// these two bit shifter values are meant to scale to:
//     0x00, 0x40, 0x80, 0xFF
// which needs a fix at the tail end...
uint8_t crgb_2val_fix(uint8_t val) {
	return ( (val == 0xc0) ? 0xFF : val );
}

void set_crgbi_val(uint8_t val) {
	VALR = crgb_2val_fix( val & 0xc0 );
	VALG = crgb_2val_fix( (val & 0x30) << 2);
	VALB = crgb_2val_fix( (val & 0x0c) << 4);
	//VALI = crgb_2val_fix( (val & 0x03) << 6);
	VALI = crgb_2val_fix( val << 6);
}

void docmp(uint8_t lreg, uint8_t rreg) {
	st &=~ (1<<ST_M | 1<<ST_E | 1<<ST_P);		// clear all compare flags needed
	if(reg[lreg] > reg[rreg]) {		// over target
		st |= (1<<ST_P);		// set flags as like CMP would
	} else if(reg[lreg] < reg[rreg]) {    // under target
		st |= (1<<ST_M);
	} else {			// we need to handle equal case
		st |= (1<<ST_E);
	}
}

// run exactly one instruction of a loaded program
void step_program() {
	uint8_t op, nextpe, nextpehigh, nextpelow;	// we need to both preserve the op, and normalize the compressed ones down
							// nextpe is an optimization trick
	// handle delay case immediately and exit if called for....
	if (delay_val > 0) {
		delay_val--;		// decrement
		idle();			// go idle for one cycle XXX should be pushed up to caller
		return;
	}
	
	op = program[pc];		// get the op
	// bump the pc a step, the routines for the ops are responsible for pushing past their params..
	pc++;
	//optimizations... worth the register use to save below switch sections from having to have their own version of this 10-20 times
	nextpe = program[pc];		// get the first potential param so others dont need to...
	nextpehigh = nextpe >> 4;	// high 4 bits shifter to low
	nextpelow = nextpe & 0x0f;	// high 4 bits stripped
	

	// do the compressed ops...
	if(op >= OP_JMP) {
		pc = op & 0x3f;
	} else if(op >= OP_SR) {
		reg[op & 0x0f] = nextpe;
		pc++;
	} else if(op >= OP_RRCOPY0) {
		reg[op & 0x0f] = reg[0];
	} else if(op >= OP_ICRGBI) {
		set_crgbi_val(reg[op & 0x0f]);
//	} else if(op >= OP_RSVD1) {		// compressed ??? not yet defined
//		op = 0x80;
	} else {	// normal ops....
		// op is normal now, do the big-switch
		switch(op) {
			case OP_IDLY:
				delay_val = ((uint32_t )reg[nextpelow]) << nextpehigh;
				pc++;
				break;
			case OP_DLY16:
				delay_val = ((uint32_t)nextpe) << 16;
				pc++;
				break;
			case OP_DLY:
				delay_val = nextpe;
				pc++;
				break;
			case OP_DLY8:
				delay_val = ((uint32_t)nextpe) << 8;
				pc++;
				break;
			case OP_RED:
				VALR = nextpe;
				pc++;
				break;
			case OP_GREEN:
				VALG = nextpe;
				pc++;
				break;
			case OP_BLUE:
				VALB = nextpe;
				pc++;
				break;
			case OP_BRIGHT:
				VALI = nextpe;
				pc++;
				break;
			// special case, RGBI takes the intensity value first, and fall into RGB code
			case OP_RGBI:
				VALI = nextpe;
				pc++;
			case OP_RGB:
				VALR = program[pc];		// can't use nextpe here as RGBI may have yoinked it
				pc++;
				VALG = program[pc];
				pc++;
				VALB = program[pc];
				pc++;
				break;
			case OP_IRED:
				VALR = reg[nextpelow];
				pc++;
				break;
			case OP_IGREEN:
				VALG = reg[nextpelow];
				pc++;
				break;
			case OP_IBLUE:
				VALB = reg[nextpelow];
				pc++;
				break;
			case OP_IBRIGHT:
				VALI = reg[nextpelow];
				pc++;
				break;
			case OP_CRGB:
				VALR = (nextpe & 0xf0);
				VALG = (nextpe & 0x0c) << 4;
				VALB = (nextpe & 0x03) << 6;
				pc++;
				break;
			case OP_CRGBI:
				set_crgbi_val(nextpe);
				pc++;
				break;
			case OP_CMPRR:
				pc++;
				docmp(nextpehigh, nextpelow);
				break;
			case OP_BEQ:
				if(bitRead(st, ST_E)) {
					pc = nextpe;
				} else {
					pc++;
				}
				break;
			case OP_BNE:
				if(! bitRead(st, ST_E)) {
					pc = nextpe;
				} else {
					pc++;
				}
				break;
			case OP_RND:
				reg[0] = prand();
				break;
			case OP_RRCOPY:
				reg[nextpehigh] = reg[nextpelow];
				pc++;
				break;
			case OP_RRSWAP:
				{ uint8_t tmp = reg[nextpelow];
				reg[nextpelow] = reg[nextpehigh];
				reg[nextpehigh] = tmp;
				}
					pc++;
				break;
			// special case, IRGBI falls through to RGBI, looking a bit ahead for it's part
			case OP_IRGBI:
				VALI = reg[ program[pc+1] & 0x0f ];
			case OP_IRGB:
				VALR = reg[nextpehigh];
				VALG = reg[nextpelow];
				pc++;
				VALB = reg[program[pc] >> 4];
				pc++;
				break;
			case OP_STEPTO:
				pc++;
				docmp(nextpehigh, nextpelow);
				if(st & (1<<ST_P)) {
					reg[nextpehigh]--;
				} else if(st & (1<<ST_M)) {
					reg[nextpehigh]++;
				}
				break;
			case OP_DEEPSLEEP:
				//ist |= IST_DEEPSLEEP;
				deepsleep();
				break;
			case OP_NOP:
				// do nothing.. putting the NO in NOP
				break;
			case OP_EOP:
				pc = 0x00;			// loops program to beginning
				break;
			default:
				break;
		}
		// any post-instruction operations...
	}
}

